{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 问题：你知道鸭子模型吗？\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "面试官想从这道题中考察面试人什么？\n",
    "考官想通过这道题了解一下大家对 Python 语言的动态特性和协议接口的理解。\n",
    "\n",
    "答案\n",
    "鸭子类型就是对象的类型无关紧要，只要实现了特定的协议（接口）即可。\n",
    "\n",
    "拓展\n",
    "鸭子类型 (Duck typing) 这个概念源自于美国印第安纳州的诗人詹姆斯・惠特科姆・莱利（James Whitcomb Riley,1849-1916）的诗句：\n",
    "\n",
    "”When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”\n",
    "\n",
    "从一段最简单的代码开始：\n",
    "\n",
    "class Duck():\n",
    "  def walk(self):\n",
    "    print('I walk like a duck')\n",
    "  def swim(self):\n",
    "    print('i swim like a duck')\n",
    "\n",
    "class Person():\n",
    "  def walk(self):\n",
    "    print('this one walk like a duck')\n",
    "  def swim(self):\n",
    "    print('this man swim like a duck')\n",
    "这个例子中，Person 类拥有跟 Duck 类一样的方法，当有一个函数调用某个类的 walk() 和 swim() 方法，如果函数不去检查对象的类型是 Duck 类还是 Person 类，只要他拥有 walk() 和 swim() 方法，就可以正确的被调用。同理，当我们想自己定义一个新的序列类 LikeSeq，我们就需要实现__getitem__，__len__等方法，然后你创建一个对象 seq=LikeSeq()，你可以像调用其它序列对象一样去用 len(seq) 获得该序列的大小，用 seq[0] 去索引序列的第一个元素。\n",
    "\n",
    "鸭子类型是动态类型的一种风格。在这种风格中，一个重要的原则是：“面向接口编程，而不是面向实现编程”。其中传递给函数或方法的对象支持运行时该对象期望的所有方法签名和属性。对象的类型本身并不重要。相反，对象应该支持在其上调用的所有方法 / 属性。在鸭子类型中，我们不在函数原型或方法中声明参数类型。这意味着编译器无法进行类型检查。真正重要的是，对象在 运行时是否具有特定的方法 / 属性。因此，动态语言通常会支持鸭子类型。\n",
    "\n",
    "面试官追问：那么白鹅类型又是什么呢？\n",
    "白鹅类型是指对接口有明确定义，只要 cls 是抽象基类，即 cls 的元类是 abc.ABCMeta，就可以使用 isinstance(obj, cls)。\n",
    "\n",
    "鸭子类型没有明确的接口，只是遵循了一定的协议。所以，调用下面这个方法时就存在一定的弊端：\n",
    "\n",
    "def fun(obj):\n",
    "    obj.walk()\n",
    "比如，如果调用的对象提供了 walk 方法，一切都会运行正常，但是如果没有，那就会直接报错。所以，鸭子类型必须遵循编码约定，文档和测试驱动的方法。\n",
    "\n",
    "from collections import abc\n",
    "\n",
    "class LikeSequence: # 鸭子类型\n",
    "    def __init__(self, components):\n",
    "        self._components = components\n",
    "\n",
    "    def __getitem__(self, item):\n",
    "        return self._components[item]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self._components)\n",
    "\n",
    "class RealSequence(abc.Sequence): # 白鹅类型\n",
    "\n",
    "    def __init__(self, components):\n",
    "        self._components = components\n",
    "\n",
    "    def __getitem__(self, item):\n",
    "        return self._components[item]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self._components)\n",
    "\n",
    ">>> obj1 = LikeSequence(list('abcde'))\n",
    ">>> print(isinstance(obj1, abc.Sequence))\n",
    "False\n",
    ">>> obj2 = RealSequence(list('abcde'))\n",
    ">>> print(isinstance(obj2, abc.Sequence))\n",
    "True\n",
    ">>> obj1.count('a') # YourSequence中没有count方法\n",
    "AttributeError: 'LikeSequence' object has no attribute 'count'\n",
    ">>> obj2.count('a') # 白鹅类型Sequence继承后能够自动获得抽象基类的方法\n",
    "1\n",
    "Tips: 我们可以把鸭子类型看成一种模仿行为，它的行为像某种类型但是它的类型又不是。\n",
    "\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}